package com.sun.btrace.client;

import java.io.BufferedReader;
import java.io.Console;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Properties;
import java.util.Vector;

import sun.misc.Signal;
import sun.misc.SignalHandler;

import com.sun.btrace.CommandListener;
import com.sun.btrace.cmd.CommandCase;
import com.sun.btrace.comm.Command;
import com.sun.btrace.comm.ConfBody;
import com.sun.btrace.comm.ConfCommand;
import com.sun.btrace.comm.DataCommand;
import com.sun.btrace.comm.ErrorCommand;
import com.sun.btrace.comm.ExitCommand;
import com.sun.btrace.comm.OkayCommand;
import com.sun.btrace.comm.SecurityPWD;
import com.sun.btrace.comm.WireIO;
import com.sun.btrace.comm.ConfCommand.OPERATOR;

public class Admin {
	private static Admin instance;

	private String host;
	private int port;
	public final Console con;
	private  PrintWriter out;
	private  BufferedReader in;
	private Vector<String> cmds ;
	
	public static final String TAG = "admin=>";

	private volatile boolean debugMode;
	private volatile boolean trackRetransforms;
	private volatile boolean unsafeMode;
	private volatile boolean dumpClasses;
	private volatile boolean isRemoteMode ;

	private volatile String dumpDir;
	private volatile String scriptOutputFile;

	private volatile String password;
	private String newPassword ;
	
	Client client ;
	@cmd_show
	public void debug() {
		debugMode = !debugMode;
		println("debugMode=" + debugMode);
	}
	@cmd_show
	public void host() throws IOException {
		print("input the host:") ;
		host=in.readLine();
		println("host=" + host);
	}
	@cmd_show
	public void retrans() {
		trackRetransforms = !trackRetransforms;
		println("trackRetransforms=" + trackRetransforms);
	}
	@cmd_show
	public void unsafe() {
		unsafeMode = !unsafeMode;
		println("unsafeMode=" + unsafeMode);
	}
	@cmd_show
	public void dumpc() {
		dumpClasses = !dumpClasses;
		println("dumpClasses=" + dumpClasses);
	}
	@cmd_show
	public void dump_dir() {
		println("dumpDir=" + dumpDir);
	}
	@cmd_show
	public void set_dump_dir() throws IOException {
		print("dumpDir=" + dumpDir + "\n" + "input new location: ");
		dumpDir = in.readLine();
		println("dumpDir=" + dumpDir + "\n");
	}
	@cmd_show
	public void script_output_file() {
		println("scriptOutputFile=" + scriptOutputFile);
	}
	@cmd_show
	public void set_script_output_file() throws IOException {
		print("scriptOutputFile=" + scriptOutputFile + "\n"
				+ "input new scriptOutputFile: ");
		scriptOutputFile = in.readLine();
		println("scriptOutputFile=" + scriptOutputFile + "\n");
	}
	@cmd_show
	public void password() throws IOException {
		print("input password: ");
		password = SecurityPWD.generatePassword(in.readLine() );
		println("password setted");
	}
	public void password_show() throws IOException {
		print("password=" + password + "\n");
	}
	protected Admin() {
		port = 2020;
		host = "localhost";
		this.debugMode = true;
		this.dumpClasses = true;
		this.dumpDir = "h:\\btracedump\\";
		this.scriptOutputFile = ".";
		this.trackRetransforms = false;
		this.unsafeMode = true;
		con = System.console();
		out = (con != null) ? con.writer() : new PrintWriter(System.out);
		in = (con != null) ? new BufferedReader(con.reader())
				: new BufferedReader(new InputStreamReader(System.in));
		;
		this.cmds = new Vector<String>();
		 client = new Client(port, ".", this.debugMode,
				this.trackRetransforms, this.unsafeMode, this.dumpClasses,
				this.dumpDir);
		list_cmd();
	}
	
	public void list_cmd() {
 		Method[] ms = Admin.class.getDeclaredMethods();
//		String cmds = "";
		for (Method m : ms) {
			String name = m.getName();
			if (m.getAnnotation(cmd_show.class) != null )
//				cmds += ("\t" + m.getName().replace("_", "-")) + "\n";
				this.cmds.add(m.getName().replace("_", "-"))  ;
		}
//		println(cmds);
	}
	@cmd_show
	public void ls(){
		String cmd_list = Arrays.toString(this.cmds.toArray()) ;
		println(cmd_list) ;
	}
	@cmd_show
	public void port() {
		println("port=" + port);
	}
	@cmd_show
	public void set_port() throws NumberFormatException, IOException {
		print("input port: ");
		port = Integer.parseInt(in.readLine());
		println("cmd done. port=" + port);
	}
	@cmd_show
	public void start_agent() throws IOException {
		if(isAgentRunning()){
			println("agent already startted on port "+ port) ;
			return ;
		}
		print("input the pid: ");
		String pid = in.readLine();
		password();
		Client client = new Client(port, ".", this.debugMode,
				this.trackRetransforms, this.unsafeMode, this.dumpClasses,
				this.dumpDir);
		client.attach(pid);
		println("started the agent, set password for it") ;
		
		Socket sock = new Socket("localhost", port);
		ObjectOutputStream oos = new ObjectOutputStream(sock.getOutputStream());
		ObjectInputStream ois = new ObjectInputStream(sock.getInputStream());
		ConfBody body = new ConfBody();
		ConfCommand r = new ConfCommand();
		r.setBody(body);
		r.setOperator(OPERATOR.SETPASSWORD) ;

		body.setPassword(password);
		WireIO.write(oos, r);
		Command cmd = WireIO.read(ois);
		if (cmd.getType() == Command.SUCCESS) {
			println("password setted.");
		} else
			println("password set fail.");

		ois.close();
		oos.close();
		sock.close();
	}
	public Client getClient(){
		if(client != null )
			try {
				client.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				//e.printStackTrace();
			}
		client = new Client(port, ".", this.debugMode,
						this.trackRetransforms, this.unsafeMode, this.dumpClasses,
						this.dumpDir);	
		client.setPassword(password) ;
		client.setHost(this.host) ;
		return client ;
		
	}
	public  void registerExitHook(final Client client) {
		println("registering shutdown hook");
		Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
			public void run() {
				if (client != null) {
					try {
						println("sending exit command");
						client.sendExit(0);
						client.close();
					} catch (IOException ioexp) {
						println(ioexp.toString());
					}
				}
			}
		}));
	}

	
	@cmd_show
	public void get_config() throws UnknownHostException, IOException {
		Socket sock = new Socket(host, port);
		ObjectOutputStream oos = new ObjectOutputStream(sock.getOutputStream());
		ObjectInputStream ois = new ObjectInputStream(sock.getInputStream());
		
		ConfCommand r = new ConfCommand();
		r.setOperator(OPERATOR.GETCONF) ;
		ConfBody body = new ConfBody();
		body.setPassword(this.password) ;
		r.setBody(body);
		
		WireIO.write(oos, r);
		Command cmd = WireIO.read(ois);
		if (cmd.getType() == Command.CONF) {
			print("command done, detail as below: ");
			ConfBody bb = ((ConfCommand) cmd).getBody();
			this.debugMode = bb.isDebugMode();
			trackRetransforms = bb.isTrackRetransforms();
			unsafeMode = (bb.isUnsafeMode());
			dumpClasses = (bb.isDumpClasses());
			dumpDir = (bb.getDumpDir());
			scriptOutputFile = (bb.getScriptOutputFile());
			isRemoteMode = bb.isRemoteMode();
//			password = (bb.getPassword());
			conf();
		} else{
			println("command fail.");
			ErrorCommand err=(ErrorCommand)cmd;
			println(err.getCause().getMessage()) ;
		}

		ois.close();
		oos.close();
		sock.close();
	}
	@cmd_show
	public void set_config() throws UnknownHostException, IOException {
		println(list_self());
		print("configure as above, press Y to commit the configure to the agent(Y/N):") ;
		String c = "" ;
		while(!c.toLowerCase().equals("y")&&!c.toLowerCase().equals("n")) 
			c = in.readLine() ;
		if(!c.toLowerCase().equals("y")){
			println("user cancel the action") ;
			return ;
		}
		Socket sock = new Socket(host, port);

		ObjectOutputStream oos = new ObjectOutputStream(sock.getOutputStream());
		ObjectInputStream ois = new ObjectInputStream(sock.getInputStream());
		println("......connect to server");
		ConfBody body = getCurrentConfig();
		ConfCommand r = new ConfCommand();
		r.setBody(body);
		r.setOperator(OPERATOR.SETCONF) ;

		WireIO.write(oos, r);
		Command cmd = WireIO.read(ois);
		if (cmd.getType() == Command.SUCCESS) {
			println("command done.");
		} else
			println("command fail.");

		ois.close();
		oos.close();
		sock.close();
	}
	public ConfBody getCurrentConfig(){
		ConfBody body = new ConfBody();

		body.setDebugMode(debugMode);
		body.setTrackRetransforms(trackRetransforms);
		body.setUnsafeMode(unsafeMode);
		body.setDumpClasses(dumpClasses);
		body.setDumpDir(dumpDir);
		body.setScriptOutputFile(scriptOutputFile);
		body.setPassword(password);
		body.setRemoteMode(isRemoteMode) ;
		return body ;
	}
	public void print(String s) {
		out.print(TAG + s);
		out.flush();
	}

	public void println(String s) {
		out.print(TAG + s+"\n");
	}
	public static Admin getAdmin() {
		if (instance == null)
			instance = new Admin();
		return instance;
	}
	
	@cmd_show
	public void conf() {
		println(list_self());
	}

	private String list_self() {
		String r = "\n"+
				"debugMode=" + this.debugMode + "\n" + "trackRetransforms="
				+ trackRetransforms + "\n" + "unsafeMode=" + unsafeMode + "\n"
				+ "isRemoteMode="+isRemoteMode+"\n"
				+ "dumpClasses=" + dumpClasses + "\n" + "dumpDir=" + dumpDir
				+ "\n" + "scriptOutputFile=" + scriptOutputFile + "\n"
				+ "password=" + password;
		return r;
	}
	
	@cmd_show
	public void reset_password() throws IOException{
		print("input new password:") ;
		this.newPassword = SecurityPWD.generatePassword(in.readLine());
		Socket sock = new Socket(host, port);

		ObjectOutputStream oos = new ObjectOutputStream(sock.getOutputStream());
		ObjectInputStream ois = new ObjectInputStream(sock.getInputStream());
		println("......connect to server");
		ConfBody body = new ConfBody();
		ConfCommand r = new ConfCommand();
		r.setBody(body);
		r.setOperator(OPERATOR.RESETPASSWORD) ;
		body.setPassword(password);
		body.setNewPassword(this.newPassword);
		WireIO.write(oos, r);
		Command cmd = WireIO.read(ois);
		if (cmd.getType() == Command.SUCCESS) {
			this.password= this.newPassword;
			println("command done.");
		} else if(cmd.getType() == Command.ERROR){
			println("command fail.");
			ErrorCommand err=(ErrorCommand)cmd;
			err.getCause().printStackTrace(out) ;
		}

		ois.close();
		oos.close();
		sock.close();
	}
		
	private boolean isAgentRunning(){
		Socket sock;
		try {
			sock = new Socket(host, port);
			ObjectOutputStream oos = new ObjectOutputStream(sock.getOutputStream());
			ObjectInputStream ois = new ObjectInputStream(sock.getInputStream());
			WireIO.write(oos, new OkayCommand());
			Command cmd = WireIO.read(ois);
			if(cmd.getType()== Command.SUCCESS)
				return true ;
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			return false ;
		}
		return true ;

	}
	public int getPort(){
		return this.port ;
	}
	public PrintWriter getOut() {
		return out;
	}
	public BufferedReader getIn() {
		return in;
	}
	public void setOut(PrintWriter fout) {
		 this.out=fout;
	}
	public String getHost() {
		return host;
	}
	public void setHost(String host) {
		this.host = host;
	}
	protected void loop() {
		String ora_CMD ="";
		String[] cmds_input ;
		
		while (true) {
			print("");
			try {
				ora_CMD = in.readLine();
				if(ora_CMD.replace(" ", "").equals("")){
					//println("");
					continue;
				}
				cmds_input = ora_CMD.split("\\ +");
				//println(Arrays.toString(cmds_input));
				if (cmds_input[0].toLowerCase().equals("exit"))
					break;
				String CMD_First=cmds_input[0] ;
				
				if( this.cmds.contains(CMD_First)){
					CMD_First = CMD_First.replace("-", "_");
					Method m = this.getClass().getDeclaredMethod(CMD_First);
					m.invoke(this);
				}
				else{
					//println(Arrays.toString(Arrays.copyOfRange(cmds_input, 1, cmds_input.length))) ;
					CMD_First = CMD_First.replace("-", "_");
					CommandCase externCmd = (CommandCase)Class.forName("com.sun.btrace.cmd."+CMD_First).newInstance() ;
					externCmd.setContext(this);
					externCmd.cmd(Arrays.copyOfRange(cmds_input, 1, cmds_input.length));
				}
			} catch (Exception e) {
				// TODO Auto-generated catch block
				println("-----------------");
				println(e.toString());
				e.printStackTrace(out);
				println("-----------------");

			}
		}
	}
	protected void handleCMD(String cmd_input){
		
	}
	
	public   void registerSignalHandler(final Client client) {
		print("registering signal handler for SIGINT");
		Signal.handle(new Signal("INT"), new SignalHandler() {
			public void handle(Signal sig) {
				try {
					println("Please enter your option:\n");
					println("\t1. exit\n\t2. send an event\n\t3. send a named event\n\t4.exit current cmd");
					out.flush();
					String option = in.readLine();
					option = option.trim();
					if (option == null) {
						return;
					}
					if (option.equals("1")) {
						System.exit(0);
					} else if (option.equals("2")) {
						println("sending event command");
						client.sendEvent();
					} else if (option.equals("3")) {
						println("Please enter the event name: ");
						String name = con.readLine();
						if (name != null) {
							println("sending event command");
							client.sendEvent(name);
						}
					}else if (option.equals("4")) {
							println("exit current cmd");
							client.sendExit(0);
							client.close();
							println("");
					}
					else {
						println("invalid option!\n");
					}
				} catch (IOException ioexp) {
					println(ioexp.getMessage());
				}
			}
		});
	}
	public  CommandListener createCommandListener(Client client) {
		return new CommandListener() {
			public void onCommand(Command cmd) throws IOException {
				int type = cmd.getType();
				if (cmd instanceof DataCommand) {
					((DataCommand) cmd).print(out);
					out.flush();
				} else if (type == Command.EXIT) {
					ExitCommand ecmd = (ExitCommand) cmd;
//					System.exit(ecmd.getExitCode());
					Admin.this.client.close();
					println("recive exit cmd, and will return");
					return ;
				} else if (type == Command.ERROR) {
					ErrorCommand ecmd = (ErrorCommand) cmd;
					Throwable cause = ecmd.getCause();
					if (cause != null) {
						println(cause.getMessage());
						cause.printStackTrace(out ) ;
					}
				}
			}
		};
	}
	@Retention(RetentionPolicy.RUNTIME)
	@Target(ElementType.METHOD)
	public @interface cmd_show{
		
	}
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Admin a = Admin.getAdmin();
		System.out.print("		   (__)\n");
		System.out.print("		   /oo\\\\________\n");
		System.out.print("		   \\  /         \\---\\\n");	
		System.out.print("		     \\   /       /  \\ \\   \\\n");
		System.out.print("		       \\\\_|___\\\\_|/    *\n");
		System.out.print("		         ||   YY|  \n");
		System.out.print("		         ||    ||    \n");
		System.out.print("      **********************************\n");
		System.out.print("    ****************************************\n");
		System.out.print("**********************************************\n");

		a.loop();
	}
 
}
